/********************************************************************
 * File: trace.c
 *
 * main function for the pvtrace utility.
 *
 * Author: M. Tim Jones <mtj@mtjones.com>
 *
 */

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <assert.h>
#include <string>
#include <iostream>
#include <sstream>
#include <bitset>

#include <map>
#include <set>
#include <list>

struct EdgeInfo
{
    std::string caller;
    std::string callee;
    bool operator<(const EdgeInfo &other) const
    {
        // printf("operator<(const EdgeInfo &lhs, const EdgeInfo &rhs)\n");
        if (caller != other.caller)
            return caller < other.caller;
        else
            return callee < other.callee;
    }
};

struct ProfileInfo
{
    bool isEnter;
    std::string caller;
    std::string callee;
    std::string callerBaseName;
    std::string calleeBaseName;
};

struct Addr2LineInfo
{
    std::string fun;
    std::string baseName;
};

struct Tracer
{
    std::map<unsigned int, Addr2LineInfo> addr2line;
    std::set<EdgeInfo> edgeInfoSet;
    std::list<ProfileInfo> profileInfoList;
    std::string imageName;
    std::string watchFunction;
    bool traceSwitch = true;

    void setImageName(std::string s)
    {
        imageName = s;
    }
    void setWatchFunction(std::string s)
    {
        watchFunction = s;

        if (!watchFunction.empty())
            traceSwitch = false;
    }

    std::string getAdd2LineOutput(unsigned int address)
    {
        std::ostringstream ss;
        ss << "addr2line -e " << imageName << " -f -s 0x" << std::hex << address;

        FILE *p = popen(ss.str().c_str(), "r");

        if (p == NULL)
            return "unknown";

        std::string output;

        while (1)
        {
            char buf[256];

            size_t len = fread(buf, 1, 256, p);

            if (!len)
                break;
            output.append(buf, len);
        }

        pclose(p);

        return output;
    }

    void parseOuput(std::string output, std::string &fun, std::string &basename)
    {
        size_t lastPos = output.find('\n');

        if (lastPos == std::string::npos)
        {
            fun = "unknown";
            return;
        }

        fun = output.substr(0, lastPos);
        if (fun == "??")
            fun = "unknown";
        basename = output.substr(lastPos);
    }

    void parseAddress(unsigned int address, std::string &fun, std::string &basename)
    {
        if (addr2line.find(address) == addr2line.end())
        {
            parseOuput(getAdd2LineOutput(address), fun, basename);
            addr2line.insert({address, {fun, basename}});
        }
        else
        {
            const auto &item = addr2line[address];
            fun = item.fun;
            basename = item.baseName;
        }
    }

    void AddProfileInfo(bool isEnter, unsigned int address1, unsigned int address2)
    {
        ProfileInfo info;

        info.isEnter = isEnter;
        parseAddress(address1, info.callee, info.calleeBaseName);
        parseAddress(address2, info.caller, info.callerBaseName);

        if ((!watchFunction.empty()) && (info.callee == watchFunction))
        {
            traceSwitch = isEnter;
        }

        if (traceSwitch)
        {
            edgeInfoSet.insert({info.caller, info.callee});
            profileInfoList.push_back(info);
        }
    }

    void EdgeInfo2SVG()
    {
        FILE *dot = fopen("graph.dot", "w");
        fprintf(dot, "digraph quickjs {\n");

        for (const auto &item : edgeInfoSet)
        {
            fprintf(dot, "%s -> %s;\n", item.caller.c_str(), item.callee.c_str());
        }
        fprintf(dot, "}\n");
        fclose(dot);

        system("dot -Tsvg graph.dot -o graph.svg");
    }
};

int main(int argc, char *argv[])
{

    if (argc < 2)
    {
        printf("Usage: pvtrace <image>\n\n");
        exit(-1);
    }

    Tracer tracer;
    tracer.setImageName(argv[1]);

    if (argc == 3)
    {
        tracer.setWatchFunction(argv[2]);
    }

    FILE *tracef = fopen("trace.txt", "r");

    if (tracef == NULL)
    {
        printf("Can't open trace.txt\n");
        exit(-1);
    }

    while (!feof(tracef))
    {
        unsigned int address1, address2;
        char type;

        fscanf(tracef, "%c0x%x 0x%x\n", &type, &address1, &address2);
        tracer.AddProfileInfo(type == 'E', address1, address2);
    }

    fclose(tracef);

    tracer.EdgeInfo2SVG();
    return 0;
}
